/* 
 * $Id: JGraphEditorKit.java,v 1.3 2006/06/05 19:48:25 gaudenz Exp $
 * Copyright (c) 2001-2005, Gaudenz Alder
 * 
 * All rights reserved.
 * 
 * See LICENSE file for license details. If you are unable to locate
 * this file please contact info (at) jgraph (dot) com.
 */
package com.jgraph.editor;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.swing.ImageIcon;
import javax.swing.tree.MutableTreeNode;

import org.jgraph.JGraph;
import org.jgraph.graph.AttributeMap;
import org.jgraph.graph.DefaultEdge;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.DefaultPort;
import org.jgraph.graph.Edge;
import org.jgraph.graph.GraphCell;
import org.jgraph.graph.GraphConstants;

import com.jgraph.editor.JGraphEditorAction.Bundle;
import com.jgraph.pad.graph.JGraphpadBusinessObject;
import com.jgraph.pad.graph.JGraphpadGraphConstants;
import com.jgraph.pad.tool.JGraphpadEdgeTool;
import com.jgraph.pad.tool.JGraphpadVertexTool;

/**
 * Holds references to actions and mouse tools and updates their states.
 */
public class JGraphEditorKit {

	/**
	 * Defines the default port locations.
	 */
	public Point2D[] defaultPortLocations = new Point2D[] { null };

	/**
	 * Defines the default border color.
	 */
	public Color defaultBorderColor = Color.BLACK;

	/**
	 * Defines the default edge font.
	 */
	public Font defaultEdgeFont = GraphConstants.DEFAULTFONT.deriveFont(10);

	/**
	 * Defines the default end and begin decorations for edges.
	 */
	public int defaultEndDecoration = GraphConstants.ARROW_TECHNICAL,
			defaultBeginDecoration = GraphConstants.ARROW_NONE;

	/**
	 * Holds the (name, action) and (name, tool) pairs respectively.
	 */
	protected Map actions = new Hashtable(), tools = new Hashtable();

	/**
	 * Holds all action bundles.
	 */
	protected List bundles = new ArrayList(2);

	/**
	 * Adds the specified action to the kit.
	 * 
	 * @param action
	 *            The action to add.
	 * @return Returns the previous action for the same name.
	 * 
	 * @see JGraphEditorAction#getName()
	 */
	public Object addAction(JGraphEditorAction action) {
		if (action != null)
			return actions.put(action.getName(), action);
		return null;
	}

	/**
	 * Adds all actions in the specified bundle and stores a reference to the
	 * bundle for later update of the actions.
	 * 
	 * @param bundle
	 *            The bundle to add the actions from.
	 */
	public void addBundle(JGraphEditorAction.Bundle bundle) {
		JGraphEditorAction[] a = bundle.getActions();
		for (int i = 0; i < a.length; i++)
			addAction(a[i]);
		bundles.add(bundle);
	}

	/**
	 * Returns the action for the specified name or <code>null</code> if no
	 * such action exists.
	 * 
	 * @param name
	 *            The name that identifies the action.
	 * @return Returns the action with the name <code>name</code> or
	 *         <code>null</code>.
	 */
	public JGraphEditorAction getAction(String name) {
		if (name != null)
			return (JGraphEditorAction) actions.get(name);
		return null;
	}

	/**
	 * Adds the specified tool to the kit.
	 * 
	 * @param tool
	 *            The tool to add.
	 * @return Returns the previous tool for the same name.
	 * 
	 * @see JGraphEditorTool#getName()
	 */
	public Object addTool(JGraphEditorTool tool) {
		if (tool != null) {
			return tools.put(tool.getName(), tool);
		}
		return null;
	}

	/**
	 * Returns the tool for the specified name or <code>null</code> if no such
	 * tool exists.
	 * 
	 * @param name
	 *            The name that identifies the tool.
	 * @return Returns the tool with the name <code>name</code> or
	 *         <code>null</code>.
	 */
	public JGraphEditorTool getTool(String name) {
		if (name != null)
			return (JGraphEditorTool) tools.get(name);
		return null;
	}

	/**
	 * This is messaged from the application when the kit should update the
	 * state of its actions and tools. This implementation updates all
	 * registered bundles.
	 * 
	 * @see JGraphEditorAction.Bundle#update()
	 */
	@SuppressWarnings("unchecked")
	public void update() {
		Iterator<Bundle> bundleIterator = bundles.iterator();
		while (bundleIterator.hasNext()) {
			bundleIterator.next().update();
		}

		Iterator<JGraphEditorTool> toolIterator = tools.values().iterator();
		while (toolIterator.hasNext()) {
			toolIterator.next().update();
		}
	}

	/**
	 * @return Returns the actions.
	 */
	public Map getActions() {
		return actions;
	}

	/**
	 * @param actions The actions to set.
	 */
	public void setActions(Map actions) {
		this.actions = actions;
	}

	/**
	 * @return Returns the bundles.
	 */
	public List getBundles() {
		return bundles;
	}

	/**
	 * @param bundles The bundles to set.
	 */
	public void setBundles(List bundles) {
		this.bundles = bundles;
	}

	/**
	 * @return Returns the tools.
	 */
	public Map getTools() {
		return tools;
	}

	/**
	 * @param tools The tools to set.
	 */
	public void setTools(Map tools) {
		this.tools = tools;
	}

	/**
	 * Hook for subclasses to construct groups with default user objects. This
	 * implementation invokes {@link #createVertexUserObject(Object)}and passes
	 * the return value to {@link #createGroup(Object)}.
	 *
	 * @return Returns a new group with a default user object.
	 */
	public GraphCell createGroup() {
		return createGroup(createVertexUserObject(null));
	}

	/**
	 * Returns a new DefaultGraphCell containing the specified user object. This
	 * implementation uses {@link #createAttributeMap()}to create the map that
	 * holds the attributes for the new vertex, and
	 * {@link #configureVertex(GraphCell)}to configure the vertex.
	 *
	 * @param userObj The user object that the group should contain.
	 * @return Returns a new group.
	 */
	public GraphCell createGroup(Object userObj) {
		DefaultGraphCell vertex = new DefaultGraphCell(userObj,
				createAttributeMap());
		configureVertex(vertex);
		return vertex;
	}

	/**
	 * Invokes
	 * {@link #createVertexTool(String, Object, int, ImageIcon, boolean)}
	 * with post editing set to false.
	 *
	 * @param name Tool name.
	 * @param shape The shape of the vertices created by the vertex tool.
	 * @param icon The tool icon show in the tool bar.
	 * @param defaultValue The defaultValue for the vertices that this tool
	 *        creates.
	 */
	public JGraphpadVertexTool createVertexTool(String name,
			Object defaultValue, int shape, ImageIcon icon) {
		return createVertexTool(name, defaultValue, shape, icon, false, true);
	}

	/**
	 * Creates a new vertex tool. The method sets the shape on the created
	 * graph cell, and returns a new JGraphpadVertexTool with the specified
	 * name.
	 *
	 * @param name The name of the tool to be created.
	 * @param defaultValue The defaultValue for the vertices that this tool
	 *        creates.
	 * @param shape The shape for the vertices that this tool creates.
	 *        Use -1 for no border.
	 * @param icon The icon for the vertices that this tool creates.
	 * @param postEdit If in-place editing should be triggered after inserting
	 *        the cell.
	 * @param marqueeStyle If the tool creates vertices in a marquee style
	 *        (selecting a rectangle which to be filled by the new cell) or
	 *        drag-and-drop style (single click creates a cell with a default
	 *        size).
	 * @return Returns a new vertex tool.
	 *
	 * @see #createVertex(Object)
	 * @see JGraphpadGraphConstants#setVertexShape(Map, int)
	 * @see JGraphpadVertexTool
	 */
	public JGraphpadVertexTool createVertexTool(String name,
			Object defaultValue, int shape, ImageIcon icon, boolean postEdit,
			boolean marqueeStyle) {
		Object userObject = createVertexUserObject(defaultValue);
		GraphCell vertex = createVertex(userObject);
	
		if (shape >= 0) {
			JGraphpadGraphConstants.setVertexShape(vertex.getAttributes(),
					shape);
		} else {
			vertex.getAttributes().remove(GraphConstants.BORDERCOLOR);
			vertex.getAttributes().remove(GraphConstants.BORDER);
		}
		if (icon != null) {
			GraphConstants.setIcon(vertex.getAttributes(), icon);
		}
		if (postEdit) {
			return new JGraphpadVertexTool(name, vertex) {
				@Override
				protected void execute(JGraph graph, Object cell) {
					super.execute(graph, cell);
					graph.startEditingAtCell(cell);
				}
			};
		} else if (marqueeStyle) {
			return new JGraphpadVertexTool(name, vertex);			
		} else {
			JGraphpadVertexTool vertexTool =
				new JGraphpadVertexTool(name, vertex) {
				@Override
				protected void processMouseDraggedEvent(MouseEvent event) {
					/* do not support */
				}
			};
			vertexTool.setThreshold(0);
			vertexTool.setSingleClickSize(new Dimension(50, 50));
			return vertexTool;
		}
	}

	/**
	 * Helper method to create and return a new edge tool. This passes the
	 * return value of createEdgeUserObject to createEdge to create the graph
	 * cell. The method sets the routing on the created graph cell if it is not
	 * <code>null</code>, and returns a new JGraphpadEdgeTool with the
	 * specified name.
	 * 
	 * @param name The name of the tool to be created.
	 * @param defaultValue The defaultValue for the edges that this tool
	 *        creates.
	 * @param icon The icon to be for the edges created by the tool, null
	 *        if no icon should be drawn.
	 * @param routing The routing for the edges that this tool creates.
	 * @return Returns a new edge tool.
	 *
	 * @see #createEdgeUserObject(Object)
	 * @see #createEdge(Object)
	 * @see GraphConstants#setRouting(Map, Edge.Routing)
	 * @see JGraphpadEdgeTool
	 */
	public JGraphpadEdgeTool createEdgeTool(String name,
			Object defaultValue, ImageIcon icon, Edge.Routing routing) {
		DefaultEdge edge = createEdge(createEdgeUserObject(defaultValue));
		if (routing != null) {
			GraphConstants.setRouting(edge.getAttributes(), routing);
		}
		if (icon != null) {
			GraphConstants.setIcon(edge.getAttributes(), icon);
		}
		JGraphpadEdgeTool tool = new JGraphpadEdgeTool(name, edge);
		return tool;
	}

	/**
	 * Hook for subclasses to construct vertices with default user objects.
	 * This implementation invokes {@link #createVertexUserObject(Object)}and
	 * passes the return value to {@link #createVertex(Object)}.
	 *
	 * @return Returns a new vertex with a default user object.
	 */
	public GraphCell createVertex() {
		return createVertex(createVertexUserObject(null));
	}

	/**
	 * Returns a new DefaultGraphCell containing the specified user object. This
	 * implementation uses {@link #createAttributeMap()}to create the map that
	 * holds the attributes for the new vertex, and
	 * {@link #configureVertex(GraphCell)}to configure the vertex.
	 *
	 * @param userObj The user object that the vertex should contain.
	 * @return Returns a new vertex.
	 */
	public GraphCell createVertex(Object userObj) {
		DefaultGraphCell vertex = new DefaultGraphCell(userObj,
				createAttributeMap());
		configureVertex(vertex);
		return vertex;
	}

	/**
	 * Hook for subclasses to configure the specified vertex. This
	 * implementation sets the {@link #defaultBorderColor}and adds ports if the
	 * vertex implements the {@link MutableTreeNode}interface.
	 *
	 * @param vertex The vertex to be configured.
	 *
	 * @see #addPorts(MutableTreeNode, Point2D[])
	 */
	public void configureVertex(GraphCell vertex) {
		AttributeMap attributes = vertex.getAttributes();
		if (defaultBorderColor != null) {
			GraphConstants.setOpaque(attributes, false);
			GraphConstants.setGroupOpaque(attributes, false);
			GraphConstants.setBorderColor(attributes, defaultBorderColor);
			JGraphpadGraphConstants.setGroupResize(attributes, true);
			JGraphpadGraphConstants.setInset(attributes, 4);
		}
		if (vertex instanceof MutableTreeNode) {
			addPorts((MutableTreeNode) vertex, defaultPortLocations);
		}
	}

	/**
	 * Hook for subclasses to construct edges with default user objects. This
	 * implementation invokes {@link #createEdgeUserObject(Object)}and passes
	 * the return value to {@link #createEdge(Object)}.
	 * 
	 * @return Returns a new edge with a default user object.
	 */
	public DefaultEdge createEdge() {
		return createEdge(createEdgeUserObject(null));
	}

	/**
	 * Returns a new DefaultEdge containing the specified user object. This
	 * implementation uses {@link #createAttributeMap()}to create the map that
	 * holds the attributes for the new edge and
	 * {@link #configureEdge(GraphCell)}to configure the edge.
	 * 
	 * @param userObj
	 *            The user object that the edge should contain.
	 * @return Returns a new edge.
	 */
	public DefaultEdge createEdge(Object userObj) {
		DefaultEdge edge = new DefaultEdge(userObj, createAttributeMap());
		configureEdge(edge);
		return edge;
	}

	/**
	 * Hook for subclassers to configure the specified edge. This implementation
	 * sets the {@link #defaultEdgeFont},{@link #defaultEndDecoration}and
	 * {@link #defaultBeginDecoration}.
	 * 
	 * @param edge
	 *            The edge to be configured.
	 */
	public void configureEdge(GraphCell edge) {
		AttributeMap attributes = edge.getAttributes();
		if (defaultEdgeFont != null)
			GraphConstants.setFont(attributes, defaultEdgeFont);
		if (defaultEndDecoration != GraphConstants.ARROW_NONE)
			GraphConstants.setLineEnd(attributes, defaultEndDecoration);
		if (defaultBeginDecoration != GraphConstants.ARROW_NONE)
			GraphConstants.setLineBegin(attributes, defaultBeginDecoration);
		if (edge instanceof MutableTreeNode)
			addPorts((MutableTreeNode) edge, defaultPortLocations);
	}
	
	/**
	 * Adds ports to <code>parent</code> using <code>offsets</code> as the
	 * port relative offsets. The method uses
	 * {@link #createPort(MutableTreeNode, Object)}and
	 * {@link #configurePort(GraphCell, Point2D)}to create the ports,
	 * configure them and add them to the parent.
	 * Note: By default the ports do not have a user object.
	 *
	 * @param parent The parent to add the ports to.
	 * @param offsets The points defining the port locations.
	 */
	protected void addPorts(MutableTreeNode parent, Point2D[] offsets) {
		for (int i = 0; i < offsets.length; i++) {
			GraphCell port = createPort(parent, createPortUserObject(null));
			configurePort(port, offsets[i]);
		}
	}

	/**
	 * Creates a port containing the specified user object and adds it to
	 * <code>parent</code>.
	 *
	 * @param The cell that is parent of the created port.
	 * @param userObject The user object that the port should contain.
	 * @return Returns a new port.
	 */
	public GraphCell createPort(MutableTreeNode parent, Object userObject) {
		DefaultGraphCell port = new DefaultPort(userObject);
		parent.insert(port, parent.getChildCount());
		port.setParent(parent);
		return port;
	}

	/**
	 * Hook for subclasses to configure the specified port using the
	 * <code>offset</code> as the relative location.
	 *
	 * @param port The port to be configured.
	 * @param offset The relative offset of the port.
	 */
	public void configurePort(GraphCell port, Point2D offset) {
		AttributeMap map = port.getAttributes();
		if (offset != null) {
			GraphConstants.setOffset(map, offset);
		}
	}

	/**
	 * Hook for subclasses to create a user object for ports that contains the
	 * specified value. This implementation returns the value parameter.
	 *
	 * @param value The value that the user object should contain.
	 * @return Returns a new user object containing <code>value</code>.
	 */
	public Object createPortUserObject(Object value) {
		return value;
	}

	/**
	 * Hook for subclasses to create a user object for edges that contains the
	 * specified value. This implementation returns the value.
	 *
	 * @param value The value that the user object should contain.
	 * @return Returns a new user object containing <code>value</code>.
	 */
	public Object createEdgeUserObject(Object value) {
		return value;
	}

	/**
	 * Returns a new {@link JGraphpadBusinessObject}for the specified value.
	 * This implementation just returns the value parameter.
	 *
	 * @param value The value that the user object should contain.
	 * @return Returns a new user object containing <code>value</code>.
	 *
	 * @see JGraphpadBusinessObject
	 */
	public Object createVertexUserObject(Object value) {
		return value;
	}

	/**
	 * Hook for subclasses to construct attribute map for cells. This
	 * implementation returns a new instance of {@link AttributeMap}.
	 * 
	 * @return Returns a new attribute map.
	 */
	public AttributeMap createAttributeMap() {
		return new AttributeMap();
	}
}
